package dhy.com.tomcat.four.core.porcessor;

import dhy.com.tomcat.four.core.connector.HttpConnector;
import dhy.com.tomcat.four.core.stream.HttpRequestLine;
import dhy.com.tomcat.four.core.stream.SocketInputStream;
import dhy.com.tomcat.four.error.StringManager;
import dhy.com.tomcat.four.header.HttpHeader;
import dhy.com.tomcat.four.lifeCycle.Lifecycle;
import dhy.com.tomcat.four.request.HttpRequest;
import dhy.com.tomcat.four.request.utils.RequestUtil;
import dhy.com.tomcat.four.response.HttpResponse;

import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;


/**
 * @author 大忽悠
 * @create 2022/3/9 18:03
 */
public class HttpProcessor implements Lifecycle,Runnable {
    private final Integer curProcessor;
    private final HttpConnector connector;
    private HttpRequest request;
    private HttpRequestLine requestLine = new HttpRequestLine();
    private HttpResponse response;

    /**
     * 错误信息国际化管理
     */
    protected StringManager sm = StringManager
            .getManager("dhy.com.tomcat.three.core.porcessor");

    private boolean stopped;
    private boolean available;
    private Socket socket;

    public HttpProcessor(HttpConnector httpConnector,Integer curProcessor) {
        this.connector = httpConnector;
        this.curProcessor=curProcessor;
    }

    public void process(Socket socket) {
        /**
         * 因为无法找到tomcat4中该类的源码，这里我手动模拟了一下
         */
        SocketInputStream input = null;
        OutputStream output = null;
        try {
            input = new SocketInputStream(socket.getInputStream());
            output = socket.getOutputStream();
            //创建请求和响应对象
            request = new HttpRequest(input);
            response = new HttpResponse(output);
            //设置响应的浏览头
            response.setHeader("wish", "dhy-xpy");
            System.out.println("请求和响应对象创建完毕");
            //解析请求行和响应头
            System.out.println("准备解析请求...");
            parseRequest(input, output);
            System.out.println("请求行解析完毕");
            parseHeaders(input);
            System.out.println("请求头解析完毕");
            System.out.println("当前请求的资源名为: "+request.getUri());
            //设置响应对象管理的请求对象
           response.setRequest(request);
            //区分静态和动态资源然后进行访问
            if(request.getUri().startsWith("/servlet/"))
            {
                //servlet请求资源处理
                ServletProcessor2 processor2=new ServletProcessor2();
                processor2.process(request,response);
            }else
            {
                //静态资源请求
                StaticResourceProcessor processor=new StaticResourceProcessor();
                processor.processor(request,response);
            }
            System.out.println("响应完毕");
            //关闭当前socket连接
            socket.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 功能：解析请求头
     * 这个方法是org.apache.catalina.connector.http.HttpProcessor的简化版本
     * 这个方法解析了一些“简单”的头部，像"cookie", "content-length","content-type"，忽略了其他头部
     */
    private void parseHeaders(SocketInputStream input) throws IOException, ServletException {
        //进入死循环，直到解析请求头结束跳出
        while (true) {
            HttpHeader header = new HttpHeader();
            //SocketInputStream流中获取header
            input.readHeader(header);
            String key = header.getKey();
            String value = header.getValue();
            //是否读取完毕
            if (header==null||key==null) {
                break;
            }
            //将获取到的请求头的名称和值放入HttpRquest对象中
           //如名称可以为content-length，值可以为10164(某个数字)
            request.addHeader(key,value);

            //判断是否是cookie(cookie包含在请求头中)，格式如
            //Cookie: BD_UPN=1d314753; ispeed_lsm=4; sugstore=1; BAIDUID=3E664426E867095427DD59:FG=1; BIDUPSID=3E664426E827DD59; PSTM=1440774226; BDUSS=Ex4NkJ0bEF0WTgwMwAAAA; ATS_PSID=1
            if (key.equals("cookie")) {
                //如果是cookie，还要对cookie做特殊处理
                Cookie cookies[] = RequestUtil.parseCookieHeader(value);
                for (int i = 0; i < cookies.length; i++) {
                    if (cookies[i].getName().equals("jsessionid")) {
                        //不管url是否存在jsessionid,这里通通覆盖掉
                            request.setRequestedSessionId(cookies[i].getValue());
                            request.setRequestedSessionCookie(true);
                            request.setRequestedSessionURL(false);
                    }
                    request.addCookie(cookies[i]);
                }
                //判断请求中是否有content-length
            } else if (key.equals("content-length")) {
                int n = -1;
                try {
                    //有的话直接转为int类型保存到HttpRequest对象中
                    n = Integer.parseInt(value);
                } catch (Exception e) {
                    throw new ServletException(sm.getString("request.contentLen.err"));
                }
                request.setContentLength(n);
            } else if (key.equals("content-type")) {
                //如果是content-type直接保存
                request.setContentType(value);
            }
        }
    }


    /**
     * 解析请求行
     * 请求行如：GET /myApp/ModernServlet?userName=tarzan&password=pwd HTTP/1.1
     */
    private void parseRequest(SocketInputStream input, OutputStream output) throws ServletException {
        //从SocketInputStream对象中直接获取RequestLine对象
        //解析请求第一行
        input.readRequestLine(requestLine);
        //获取请求的方式：如GET
        String method = requestLine.method;
        //获取请求的URI---携带请求参数---/myApp/ModernServlet?userName=tarzan&password=pwd
        String uri = requestLine.uri;
        //获取请求的协议版本：如HTTP/1.1
        String protocol = requestLine.protocol;
        //请求行无效的情况：没有请求的方式或没有请求的URI
        if (method.length() < 1) {
            throw new ServletException(sm.getString("request.method.invalid"));
        } else if (uri.length() < 1) {
            throw new ServletException(sm.getString("request.uri.invalid"));
        }
        //判断和获取请求行中第二项中的请求参数，并获取到URI
        int question = uri.indexOf("?");
        if (question >= 0) {
            //有参数的
            //得到"?"后面的查询字符串：如userName=tarzan&password=pwd，并保存到HttpRequest对象中
            request.setQueryString(uri.substring(question + 1, uri.length()));
            //得到URI
            uri = uri.substring(0, question);
        } else {
            //没参数的
            request.setQueryString(null);
        }

        //这里的if语句用于请求的不是以/开头的相对资源，
        //即获取以绝对地址的请求方式的URI
        if (!uri.startsWith("/")) {
            int pos = uri.indexOf("://");
            // Parsing out protocol and host name
            if (pos != -1) {
                pos = uri.indexOf('/', pos + 3);
                if (pos == -1) {
                    uri = "";
                } else {
                    uri = uri.substring(pos);
                }
            }
        }

        //检查并解析第二项中的可能存在的 jsessionid
        String match = ";jsessionid=";
        //;的起始位置
        int start = uri.indexOf(match);
        if (start >= 0) {
            //=的结束位置,
            String jsessionid = uri.substring(start + match.length());
            //将获取到的值放到HttpRequest对象中
            request.setRequestedSessionId(jsessionid);
            //当 jsessionid 被找到，也意味着会话标识符是携带在查询字符串里边，而不是在 cookie里边。需要传递true
            request.setRequestedSessionURL(true);
            //将uri去除jessionId
            uri = uri.substring(0, start);
        } else {
            request.setRequestedSessionId(null);
            request.setRequestedSessionURL(false);
        }

        //用于纠正“异常”的 URI。
        String normalizedUri = normalize(uri);

        // Set the corresponding request properties
        ((HttpRequest) request).setMethod(method);
        request.setProtocol(protocol);
        if (normalizedUri != null) {
            ((HttpRequest) request).setRequestURI(normalizedUri);
        } else {
            ((HttpRequest) request).setRequestURI(uri);
        }

        if (normalizedUri == null) {
            throw new ServletException(sm.getString("request.uri.err",uri));
        }
    }

    //纠正“异常”的 URI。例如，任何\的出现都会给/替代。
    //这里涉及到URL的编码解码：编码的格式为：%加字符的ASCII码的十六进制表示
    //如果URL不能纠正返回null，否则返回相同的或者被纠正后的 URI
    private String normalize(String path) {
        if (path == null)
            return null;
        // Create a place for the normalized path
        String normalized = path;

        //如果URI是/~开头的，除去URI前面前四个字符并加上/~
        //%7E->~
        if (normalized.startsWith("/%7E") || normalized.startsWith("/%7e"))
            normalized = "/~" + normalized.substring(4);

        //下面是解码后对应的结果,这些字符不能在URI中出现
        //%25->%   %2F->/  %2E->.  %5c->\
        //如果找到如下字符的其中一个，说明URI错误
        if ((normalized.indexOf("%25") >= 0)
                || (normalized.indexOf("%2F") >= 0)
                || (normalized.indexOf("%2E") >= 0)
                || (normalized.indexOf("%5C") >= 0)
                || (normalized.indexOf("%2f") >= 0)
                || (normalized.indexOf("%2e") >= 0)
                || (normalized.indexOf("%5c") >= 0)) {
            return null;
        }
        //如果URI仅仅只是/.则返回/
        //如www.cnblogs.com/.是可以纠正的
        if (normalized.equals("/."))
            return "/";

        //将\转为/，这里的\\是指\，第一个\是转义字符
        if (normalized.indexOf('\\') >= 0)
            normalized = normalized.replace('\\', '/');
        //URI字符串如果没有以/开头就加给它
        if (!normalized.startsWith("/"))
            normalized = "/" + normalized;
        //如果存在//，将剩下/
        //如http://www.cnblogs.com/lzb1096101803/p//4797948.html变为
        //http://www.cnblogs.com/lzb1096101803/p/4797948.html
        while (true) {
            int index = normalized.indexOf("//");
            if (index < 0)
                break;
            normalized = normalized.substring(0, index)
                    + normalized.substring(index + 1);
        }
        //如果存在/./,变成/
        while (true) {
            int index = normalized.indexOf("/./");
            if (index < 0)
                break;
            normalized = normalized.substring(0, index)
                    + normalized.substring(index + 2);
        }
        //如果存在/../
        while (true) {
            int index = normalized.indexOf("/../");
            if (index < 0)
                break;
            if (index == 0)
                return (null); // Trying to go outside our context
            int index2 = normalized.lastIndexOf('/', index - 1);
            normalized = normalized.substring(0, index2)
                    + normalized.substring(index + 3);
        }
        //URI中存在/...或者3个点以上，认为不能纠正
        if (normalized.indexOf("/...") >= 0)
            return (null);

        //返回修改后的URI
        return (normalized);
    }

    /**
     * 初始化方法
     */
    @Override
    public void initialize() {

    }

    /**
     * 启动方法
     */
    @Override
    public void start() {
          run();
    }


    /**
     * 启动处理器线程
     */
    @Override
    public void run() {
        while (!stopped) {
            Socket socket = await();
            if (socket == null){
                continue;
            }
            try {
                //处理用户请求---这里和之前一样
                process(socket);
            } catch (Throwable t) {
                System.out.println("process.invoke" +t.getMessage());
            }
            //回收处理器
            connector.recycle(this);
        }
    }

    /**
     * 阻塞，直到用户请求到来获取到这个处理器才被唤醒
     */
    private Socket await() {
        while (!available) {
            try {
                wait();
            } catch (InterruptedException e) {
            }
        }
        Socket socket = this.socket;
        available = false;
        notifyAll();
        return (socket);
    }

    /**
     * 连接器调用该方法
     */
    public void assign(Socket socket) {
        while (available) {
            try {
                wait();
            } catch (InterruptedException e) {
            }
        }
        this.socket = socket;
        available = true;
        notifyAll();
    }
}

